package net.eicp.jxcontent.template.util

import net.eicp.jxcontent.template.snippet.JXTemSnippet
import net.liftweb.common._
import net.eicp.jxcontent.template.snippet.JXTemSnippet
import scala.tools.reflect.ToolBox

object JXTemplateDispatch {
  var temContent = Map.empty[String, String]
  var temSnippetMap = Map.empty[String, JXTemSnippet]
  
  var currentCompileKeys = List.empty[String]
  
  def isCurrentCompile(key: String) = currentCompileKeys contains key
  
  def addContent(key: String, content: String) = temContent = temContent + ((key, content))
  
  def addContentAndCompile(key: String, content: String): Box[String] = {
    addContent(key, content)
    compileAndAdd(key)
  }
  
  def hasKey(key: String): Boolean = temContent contains key
  
  /**
   * 增加currentCompileKeys是为了解决对象再第一次加载的时候的同步问题。在第一次根据key获取JXTemSnippet的时候，
   * JXTemSnippet需要通过字符串eval来初始化，比较耗时，于是把这个key标记为正在编译中，如果其他请求来请求这个key的话，
   * 会直接显示snippet找不到，等到编译成功，添加进Map以后再去除这个key再currentCompileKeys的标记。
   * 如果在第一次加载中并且在标记key之前有另一个请求到来，content将会编译2次，但第一次编译成功之后会去除所有的key标记，
   * 即使是这样，也不影响最终执行效果，因为第二次编译只是把一个意义上一模一样的对象重新add进value那里。
   * 这样就避免了对apply这个方法的锁操作，提高性能，因为每个用户定制snippet都会经过这里，如果有锁操作将对性能带来极大的冲击。
   */
  def apply(key: String): JXTemSnippet = {
    if (! (temSnippetMap contains key)) {
      currentCompileKeys = key :: currentCompileKeys
      compileAndAdd(key)
      currentCompileKeys = currentCompileKeys filterNot { _ == key }
    }
    temSnippetMap(key).copyObject
  }
  
  //返回Full(错误信息)说明编译不通过
  def compileAndAdd(key: String): Box[String] = {
    val classContent = temContent getOrElse (key, {
      throw new Exception(s"key是${key}的template的键没有找到")
    })
    try {
      val dispatchSnippet = parseCode[JXTemSnippet](classContent)
	    temSnippetMap = temSnippetMap + ((key, dispatchSnippet))
	    Empty
    } catch {
      case e: Exception => Full(e.getMessage); throw e
    }
  }
  
  //临时构造一个toolbox，实际还是以seesion作用域为佳，便于并发操作
  val toolBox = {
    import scala.reflect.runtime._
    val cm = universe.runtimeMirror(getClass.getClassLoader)
    import scala.tools.reflect.ToolBox
    cm.mkToolBox()
  }
  
  private def parseCode[T](content: String) = {
    toolBox.eval(toolBox.parse(s"$content")).asInstanceOf[T]
  }
  
}